https://www.flickr.com/photos/foilman/16308782917

Of Bundles and ViewModels

Danny Preussler
ProAndroidDev
Published in
5 min readJun 22, 2017

--

One thing that struck me after I started playing with the new ViewModel announced at Google I/O was that it brings a big change for view related data. Let me explain that.

The ViewModel

The main concept behind the ViewModel is that it survives configuration changes. The most common version of this is rotation. Other configuration changes includes resizing in split screen or changing of the device language.
What happens for all of these is simple but very unique compared to other platforms: the UI gets completely destroyed and restored afterwards based on new set of resources. It means you will get completely new Activity or Fragment instances.
In the past the Activity/Fragment would store its data in onSaveInstanceState in a Bundle and would then get a Bundle back in the corresponding methods used for recreating the UI.
With the new ViewModel we don’t need this anymore as it survives these changes.

But wait

The thing that struck me with this was: that mechanism we had in place with the Bundle helped us with another approach: recreation! The Android system might kill our activity when it is in background and resources need to be freed. But as soon as the user would navigate back to the application everything gets recreated similar as it was for configuration changes.
But the new ViewModel is not build for that case!

But why?

Internally the Architecture Components uses a Fragment with setRetainInstance(true) that holds the ViewModel. This is simply a Fragment that survives configuration changes. But for the recreation case, when the Activity dies, the Fragment is dead too. So it won’t be there anymore when we come back to the app with a brand new process. Your ViewModel will be gone too.

What is the impact of this?

I think this will break a lot of apps. One of Android’s key features that I always loved is that you could open an app after weeks and it’s exactly where you left it.

I admit this might not always be what you want. Therefore, often developers refresh the content anyway in onResume() or build complicated timestamp checks to make sure data gets refreshed only if outdated.

Following the new ViewModel the content always needs to be refreshed in that given case. Or to be more precise, the view state is discarded and the content will be requested from your data layer.
On that (data) layer there could (and should) be a cache, so you still might have data ready to show. And that would be a real cache mechanism that is aware of timestamps and caching policies, other than the Bundle approach. A real cache can be even been updated via JobScheduler or push notifications while the UI was discarded.

It’s a good thing

So it’s a good thing that we take the caching responsibly out of the UI. In addition Google argues that you should not save large amount of data in a Bundle anyway. Imagine thousands of items from your Recyclerview! If you have ever run into a TransactionTooLargeException then you are aware of this problem.

Could I still…

Still you want to keep the UI state or small amounts of data saved via Bundle, which is what Google suggests:

you should never put the Country object into the saved instance state. You can put the countryId into the saved state

And this is important. Our users switch between apps all the time. They might check maps or a game and then go back to your app. It might be just seconds between those actions. And they expect that the app is in same state as it was those few seconds ago. Users don’t know if app was resumed or recreated and they don’t care! It will look like a bug and/or a bad app.

So what are your options to provide Bundle functionality when there is no Bundle in the new ViewModel?

You could store the data separately, meaning have data still retrieved from the ViewModel but the UI state via a Bundle. The problem: you start messing up your architecture. The ViewModel in terms of MVVM represents everything the view needs to show, you should not create a new layer in between!

As alternative you could add something like readFromBundle and writeToBundle methods to your ViewModel and forward the call from your Activity/Fragment.
But this way you make the whole idea of Googles ViewModel obsolete. You would read from Bundle even when the data is still there as the ViewModel is still alive. And we shouldn’t read from Bundle when we won’t need to.

How to solve this?

We only need to read from a saved Bundle when the ViewModel gets created, not when it is reused. The Activity or Fragment does not know this, so it is the wrong class to add this to. But there is the ViewModelProvider.Factory that creates the ViewModel. What we would need is to create a Bundle-aware-factory. This factory would create the ViewModel and pass the last known Bundle to it if any.

class MyModelFactory implements ViewModelProvider.Factory {   private final Bundle bundle;   public MyModelFactory(Bundle bundle) {
this.bundle = bundle;
}
@Override
public MyViewModel create(Class modelClass) {
MyViewModel viewModel = new MyViewModel();
viewModel.readFrom(bundle);
return viewModel;
}
...

Other than our idea before this Bundle is only read when needed, every time when we recreate the ViewModel.

What we would still need is the writing to the Bundle. This can be kept as you knew it from the Activity or Fragment. So override the onSaveInstance and let the ViewModel save it’s state.

@Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
viewModel.writeTo(bundle);
}

You could argue that we would write the Bundle every time we save the state although we might not need it. So is this a performance issue? No, as saving to Bundle is very fast and should be not a big issue.

Clean up

We should split the factory: One just for handling the Bundle that would forward to another factory for the creation part and just handle the Bundle. This would fulfil single responsibility principle and make our factory reusable.

class BundleAwareViewModelFactory<T extends ParcelableViewModel> implements ViewModelProvider.Factory {    private final Bundle bundle;
private final ViewModelProvider.Factory provider;
public BundleAwareViewModelFactory(@Nullable Bundle bundle,
ViewModelProvider.Factory provider) {
this.bundle = bundle;
this.provider = provider;
}
@SuppressWarnings("unchecked")
@Override
public T create(final Class modelClass) {
T viewModel = (T) provider.create(modelClass);
if (bundle != null) {
viewModel.readFrom(bundle);
}

return viewModel;
}
}

with a new version of ViewModel

public abstract class ParcelableViewModel extends ViewModel {

public abstract void writeTo(@NonNull Bundle bundle);
public abstract void readFrom(@NonNull Bundle bundle);
}

Variations

If you don’t want to extend your ViewModels from this class, maybe because you have a different inheritance tree, ParcelableViewModel could be an interface.

Erik Hellman suggests instead of using Bundle, you could build on top of Parcelable, as it’s an interface not a class. Therefore it has better segregation to android framework and can be easier replaced when testing.

Sum up

With those suggested simple changes we get the best of both worlds. You have a good way to migrate to the new ViewModel without breaking the user experience. But don’t forget: the Bundle is for small amounts of data only!

--

--

Android @ Soundcloud, Google Developer Expert, Goth, Geek, writing about the daily crazy things in developer life with #Android and #Kotlin